﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Elca.MvvmHelpers;

namespace ULSDeobfuscator {

    /// <summary>
    /// Information about a displayed column. Exists to avoid lots of duplication in the main view model
    /// </summary>
    public abstract class AbstractLogColumnViewModel : BaseViewModel {

        /// <summary>
        /// The list of known items
        /// </summary>
        private readonly ObservableHashSet<FilterChoice> _itemsList =
            new ObservableHashSet<FilterChoice>(new FilterChoice.FilterChoiceEquatable());

        private readonly List<FilterChoice> _tempFilterChoices = new List<FilterChoice>();

        /// <summary>
        /// The list of names that are filtered out, i.e. should not be displayed
        /// </summary>
        private readonly HashSet<string> _filteredOutItems = new HashSet<string>();

        public ObservableHashSet<FilterChoice> FiltersList => _itemsList;

        /// <summary>
        /// As filters are dynamic, these contain the values that are really visible (i.e. in entries not filtered out).
        /// </summary>
        private readonly HashSet<string> _visibleItems = new HashSet<string>();

        public ICommand ShowFilterCommand { get; private set; }
        public ICommand ItemFilterSelectAllCommand { get; private set; }
        public ICommand ItemFilterSelectNoneCommand { get; private set; }
        public ICommand ItemFilterCommand { get; private set; }
        public ICommand CancelItemFilterCommand { get; private set; }
        public ICommand ClearItemFilterCommand { get; private set; }

        public ICommand IncludeItemCommand { get; private set; }
        public ICommand ExcludeItemCommand { get; private set; }
        public ICommand HighlightItemCommand { get; private set; }
        public ICommand CopyItemCommand { get; private set; }

        public static IColumnCommunication ColumnCallback { protected get; set; }

        /// <summary>
        /// True if a button press is responsible for dismissing the popup of choices
        /// </summary>
        private bool _isDeliberatelyDismissingItemChoices;
        private bool _isShowingItemChoices;
        public bool IsShowingItemChoices
        {
            get { return _isShowingItemChoices; }
            set
            {
                if (_isShowingItemChoices != value) {
                    _isShowingItemChoices = value;
                    OnPropertyChanged("IsShowingItemChoices");

                    if (_isShowingItemChoices) {
                        // on opening the popup, reset the flag about correct dismissal
                        _isDeliberatelyDismissingItemChoices = false;
                    } else {
                        if (!_isDeliberatelyDismissingItemChoices) {
                            // dismissal is not intentional => cancel choice
                            OnCancelFilterItems();
                        }
                    }
                }
            }
        }
        public Visibility FilterMenuVisibility => _filteredOutItems.Count == 0 ? Visibility.Collapsed : Visibility.Visible;

        private string _filterImagePath;
        public string FilterImagePath {
            get { return _filterImagePath; }
            set {
                if (_filterImagePath != value) {
                    _filterImagePath = value;
                    OnPropertyChanged("FilterImagePath");
                }
            }
        }

        private string _filterTooltip;
        public string FilterTooltip {
            get { return _filterTooltip; }
            set {
                if (_filterTooltip != value) {
                    _filterTooltip = value;
                    OnPropertyChanged("FilterTooltip");
                }
            }
        }


        protected AbstractLogColumnViewModel() {
            ShowFilterCommand = new ConditionalCommand(OnShowFilterItemPopup, CanShowFilterItemPopup);

            ItemFilterSelectAllCommand = new ExecutableCommand(OnItemFilterSelectAll);
            ItemFilterSelectNoneCommand = new ExecutableCommand(OnItemFilterSelectNone);
            ItemFilterCommand = new ExecutableCommand(OnFilterItems);
            CancelItemFilterCommand = new ExecutableCommand(OnCancelFilterItems);
            ClearItemFilterCommand = new ExecutableCommand(OnClearItemFilter);
            IncludeItemCommand = new ExecutableCommand(OnIncludeCurrentItem);
            ExcludeItemCommand = new ExecutableCommand(OnExcludeCurrentItem);
            CopyItemCommand = new ExecutableCommand(OnCopyCurrentItem);
            HighlightItemCommand = new ExecutableCommand(OnHighlightCurrentItem);
        }

        protected abstract string ExtractName(LogEntry le);
        protected abstract ColorRule CreateColorRule(LogEntry le);

        public void RememberItem(LogEntry le, ref bool shouldBeDisplayed) {

            var filterChoice = new FilterChoice {
                ItemName = ExtractName(le),
            };
            if (_itemsList.Add(filterChoice)) { 

                // this is a new item. If we are filtering items, do not display it

                if (_filteredOutItems.Count > 0) {
                    shouldBeDisplayed = false;
                }
            }
        }

        private void OnIncludeCurrentItem() {
            SetAllChoices(false);
            _itemsList.First(p => p.ItemName == ExtractName(ColumnCallback.SelectedLogEntry)).IsSelected = true;

            DoFilter();
        }

       private void OnHighlightCurrentItem() {

            ColorRule cr = CreateColorRule(ColumnCallback.SelectedLogEntry);

            ColumnCallback.AddHighlightRule(cr);
        }

        private void SetAllChoices(bool newValue) {
            foreach (FilterChoice fc in _itemsList) {
                fc.IsSelected = newValue;
            }
        }


        private static bool IsDifferent(IList<FilterChoice> l1, ICollection<FilterChoice> l2) {

            foreach (var filterChoice in l2) {

                // Was: l1.First(..); changed to FirstOrDefault, because LMC had a crash
                FilterChoice inL1 = l1.FirstOrDefault(fc => fc.ItemName == filterChoice.ItemName);

                if (inL1 == null || filterChoice.IsSelected != inL1.IsSelected) {
                    return true;
                }
            }

            return false;
        }

        private static void CopySelection(List<FilterChoice> source, ICollection<FilterChoice> destination) {

            if (source == null) {
                App.DebugLog("Warning: Copy selection, source is null");
                return;
            }
            if (destination == null) {
                App.DebugLog("Warning: Copy selection, destination is null");
                return;
            }
            foreach (var fc in destination) {
                fc.IsSelected = source.Find(choice => choice.ItemName == fc.ItemName).IsSelected;
            }
        }

        private bool CanShowFilterItemPopup() {

            return _itemsList.Any(fc => fc.IsVisible);
        }

        public void CancelOperation() {

            if (IsShowingItemChoices) {
                OnCancelFilterItems();
            }
        }

        private void OnShowFilterItemPopup() {

            // make a copy of the current selection, to implement the 'cancel' command

            _tempFilterChoices.Clear();
            _tempFilterChoices.AddRange(_itemsList.Select(fc => new FilterChoice(fc)));

            IsShowingItemChoices = true;
        }
        private void OnItemFilterSelectAll() {

            SetAllChoices(true);
        }

        private void OnItemFilterSelectNone() {

            SetAllChoices(false);
        }

        private void OnFilterItems() {
            DismissItemChoices();

            if (IsDifferent(_tempFilterChoices, _itemsList)) {

                DoFilter();
            }
        }

        public bool IsFilteredOut(LogEntry le) {

            return _filteredOutItems.Count > 0 && _filteredOutItems.Contains(ExtractName(le));
        }

        private void DismissItemChoices() {
            _isDeliberatelyDismissingItemChoices = true;
            IsShowingItemChoices = false;
        }

        private void DoFilter() {
            _filteredOutItems.Clear();
            foreach (var ev in _itemsList.Where(fc => !fc.IsSelected).Select(fc => fc.ItemName)) {
                _filteredOutItems.Add(ev);
            }

            ColumnCallback.FilterAllEntries();
        }

        private void OnCancelFilterItems() {

            CopySelection(_tempFilterChoices, _itemsList);
            DismissItemChoices();

            ChangeFilterIcons();
        }

        private void OnClearItemFilter() {

            _filteredOutItems.Clear();
            SetAllChoices(true);
            ColumnCallback.FilterAllEntries();
        }

        public void ChangeFilterIcons() {

            FilterImagePath = _filteredOutItems.Count == 0 ? "Images/Filter16.png" : "Images/Filter2_16.png";
            FilterTooltip = (_filteredOutItems.Count > 0) ?
                "Entries explicilty filtered out: " + String.Join(", ", _filteredOutItems.ToArray()) :
                // nothing explicitly filtered out
                (_itemsList.All(fc => fc.IsVisible) ?
                    "All entries are shown" :
                    // not all visible; at least one ?
                    (CanShowFilterItemPopup() ?
                        "Some entries are filtered out by other filtering choices" :
                        "All entries are filtered out by other filtering choices"
                    )
                  );
            OnPropertyChanged("FilterMenuVisibility");
        }


        public void UpdateFilterVisibility() {

            foreach (var fc in _itemsList){
                fc.IsVisible = !fc.IsSelected || _visibleItems.Contains(fc.ItemName);
            }
        }

        public void InitVisibiltyComputation() {
            _visibleItems.Clear();
        }

        /// <summary>
        /// add data in <paramref name="le"/> to the list of visible elements
        /// </summary>
        /// <param name="le"></param>
        public void UpdateVisibilityComputation(LogEntry le) {

            _visibleItems.Add(ExtractName(le));
        }

        private void OnExcludeCurrentItem() {
            SetAllChoices(true);
            _itemsList.First(p => p.ItemName == ExtractName(ColumnCallback.SelectedLogEntry)).IsSelected = false;

            DoFilter();
        }

        private void OnCopyCurrentItem() {
            Clipboard.SetText(ExtractName(ColumnCallback.SelectedLogEntry));
        }

        public void ResetInfo() {
            _filteredOutItems.Clear();
            _itemsList.Clear();
        }

    }

    public class ProcessLogColumnViewModel : AbstractLogColumnViewModel {
        protected override string ExtractName(LogEntry le) {
            return le.Process;
        }

        protected override ColorRule CreateColorRule(LogEntry le) {

            string item = ExtractName(le);

            ColorRule cr = new ColorRule {
                Processes = new List<string> {
                    item
                },
                Name = ColumnCallback.HighlightRulePrefix + item,
            };

            return cr;
        }
    }

    public class AreaLogColumnViewModel : AbstractLogColumnViewModel {
        protected override string ExtractName(LogEntry le) {
            return le.Area;
        }

        protected override ColorRule CreateColorRule(LogEntry le) {

            string item = ExtractName(le);

            ColorRule cr = new ColorRule {
                Areas = new List<string> {
                    item
                },
                Name = ColumnCallback.HighlightRulePrefix + item,
            };

            return cr;
        }
    }

    public class CategoryLogColumnViewModel : AbstractLogColumnViewModel {
        protected override string ExtractName(LogEntry le) {
            return le.Category;
        }

        protected override ColorRule CreateColorRule(LogEntry le) {

            string item = ExtractName(le);

            ColorRule cr = new ColorRule {
                Categories = new List<string> {
                    item
                },
                Name = ColumnCallback.HighlightRulePrefix + item,
            };

            return cr;
        }
    }

    public class EventLogColumnViewModel : AbstractLogColumnViewModel {
        protected override string ExtractName(LogEntry le) {
            return le.EventID;
        }

        protected override ColorRule CreateColorRule(LogEntry le) {

            string item = ExtractName(le);

            ColorRule cr = new ColorRule {
                EventIDs = new List<string> {
                    item
                },
                Name = ColumnCallback.HighlightRulePrefix + item,
            };

            return cr;
        }
    }
    public class LevelLogColumnViewModel : AbstractLogColumnViewModel {
        protected override string ExtractName(LogEntry le) {
            return le.Level;
        }

        protected override ColorRule CreateColorRule(LogEntry le) {

            string item = ExtractName(le);

            ColorRule cr = new ColorRule {
                Levels = new List<string> {
                    item
                },
                Name = ColumnCallback.HighlightRulePrefix + item,
            };

            return cr;
        }
    }
}
